﻿using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

namespace Engine.Sgml {
    [DebuggerDisplay("CurrentChar = {CurrentChar} LineNumber = {LineNumber} LinePosition = {LinePosition}")]
    internal class SgmlStreamReader {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private StringBuilder builder;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private TextReader stream;

        public SgmlStreamReader() {
            this.builder = new StringBuilder();
            }

        public SgmlStreamReader(TextReader stream)
            : this() {
            Guard.ArgumentNotNull(stream, "stream");
            this.stream = stream;
            }

        internal void Initialize(TextReader stream) {
            Debug.Assert(stream != null);
            Initialize(stream, 1, 0);
            }

        internal void Initialize(TextReader stream, int lineNumber, int linePosition) {
            Debug.Assert(stream != null);
            Debug.Assert(lineNumber > 0);
            this.stream = stream;
            this.builder.Clear();
            LineNumber = lineNumber;
            LinePosition = linePosition;
            }

        internal void Close() {
            if (stream != null)
                stream.Close();
            }

        /// <summary>
        /// The line on which this entity was defined.
        /// </summary>
        public int LineNumber { get; private set; }

        /// <summary>
        /// The index into the line where this entity is defined.
        /// </summary>
        public int LinePosition { get; private set; }

        public bool EndOfStream {
            get { return CurrentChar == Characters.EndOfFile; }
            }

        public TextReader BaseStream {
            get { return stream; }
            }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private char currentChar;
        /// <summary>
        /// Gets the current character readed from the stream.
        /// </summary>
        public char CurrentChar {
            get { return currentChar; }
            }

        /// <summary>
        /// Reads the next character from the stream.
        /// </summary>
        /// <remarks>
        /// Windows uses:   carriage return - line feed.
        /// UNIX uses:      line feed.
        /// Macintosh uses: carriage return.
        /// </remarks>
        /// <returns>The next character from the stream.</returns>
        public char ReadChar() {
            currentChar = (char)stream.Read();
            while (!XmlConvert.IsXmlChar(currentChar) && currentChar != Characters.EndOfFile) {
                currentChar = (char)stream.Read();
                }
            switch (currentChar) {
                case Characters.CarriageReturn:
                    if (PeekChar() == Characters.LineFeed)
                        currentChar = (char)stream.Read();
                    goto case Characters.LineFeed;
                case Characters.LineFeed:
                    LinePosition = 0;
                    LineNumber++;
                    break;
                default:
                    LinePosition++;
                    break;
                }
            return currentChar;
            }

        public char PeekChar() {
            return (char)stream.Peek();
            }

        /// <summary>
        /// Returns the next character after any whitespace.
        /// </summary>
        /// <returns>The next character that is not whitespace.</returns>
        public char SkipWhitespace() {
            while (XmlConvert.IsWhitespaceChar(currentChar)) {
                ReadChar();
                }
            return currentChar;
            }

        /// <summary>
        /// Reads input until the end of the input stream or until a string of terminator characters is found.
        /// </summary>
        /// <param name="sequence">The string of terminator characters to look for.</param>
        /// <returns>The string read from the input stream.</returns>
        public string ReadToEnd(string sequence) {
            Guard.ArgumentNotNull(sequence, "sequence");
            if (sequence.Length == 0)
                return string.Empty;

            // This method scans over a chunk of text looking for the
            // termination sequence specified by the 'terminators' parameter.

            builder.Clear();
            int startLineNumber = LineNumber;
            char terminator = sequence[0];

            while (ReadChar() != Characters.EndOfFile) {
                if (currentChar == terminator) {
                    int terminatorIndex = 1;
                    int startIndex = builder.Length;
                    while (terminatorIndex < sequence.Length && sequence[terminatorIndex] == PeekChar()) {
                        terminatorIndex++;
                        builder.Append(ReadChar());
                        }
                    if (terminatorIndex == sequence.Length) {
                        builder.Remove(startIndex, sequence.Length - 1);
                        break;
                        }
                    else
                        builder.Append(currentChar);
                    }
                else
                    builder.Append(currentChar);
                }
            return builder.ToString();
            }

        public string ReadToEnd(char[] terminators) {
            Guard.ArgumentNotNull(terminators, "terminators");
            if (terminators.Length == 0)
                return string.Empty;
            builder.Clear();
            int startLineNumber = LineNumber;
            while (ReadChar() != Characters.EndOfFile) {
                if (terminators.Contains(currentChar))
                    break;
                builder.Append(currentChar);
                }
            return builder.ToString();
            }

        public void SkipToEnd(char[] terminators) {
            Guard.ArgumentNotNull(terminators, "terminators");
            if (terminators.Length == 0)
                return;
            while (ReadChar() != Characters.EndOfFile) {
                if (terminators.Contains(currentChar))
                    break;
                }
            }

        /// <summary>
        /// Returns a string giving information on how the entity is referenced and declared, walking up the parents until the top level parent entity is found.
        /// </summary>
        /// <returns>Contextual information for the entity.</returns>
        public string Context() {
            if (stream == null)
                return null;
            else
                return string.Format("Ln {0} Col {1}.", LineNumber, LinePosition);
            }
        }
    }
